/* Flow
 * Copyright 2023 Akamai Technologies, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy
 * of the License at
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing
 * permissions and limitations under the License. */

/// @file
#pragma once

#include "flow/net_flow/net_flow_fwd.hpp"
#include "flow/net_flow/server_socket.hpp"

namespace flow::net_flow::asio
{

/**
 * A net_flow::Server_socket that adds integration with boost.asio.  See net_flow::Node for a discussion of the topic of
 * boost.asio integration.  A net_flow::asio::Server_socket *is* a net_flow::Server_socket with all that functionality,
 * plus APIs (mainly async_accept()) that accomplish boost.asio-style asynchronous operations with a
 * standard `boost::asio::io_context`.
 *
 * As of this writing, `asio::Server_socket::Ptr`s are generated by `net_flow::Node::listen()` and any derivatives.
 * The underlying `io_context` (a/k/a flow::util::Task_engine)
 * is carried forward from the `net_flow::asio::Node` factory object that generated and returned the `Ptr`.  It can be
 * overwritten via set_async_task_engine().
 */
class Server_socket :
  public net_flow::Server_socket
  // Note: can't derive util::Shared_ptr_alias_holder<>, because superclass already did it (doc header disallows this).
{
public:
  // Types.

  /// Short-hand for `shared_ptr` to Server_socket.
  using Ptr = boost::shared_ptr<Server_socket>;

  // Constructors/destructor.

  /// Boring `virtual` destructor as in superclass.  See notes there.
  ~Server_socket() override;

  // Methods.

  /**
   * Pointer (possibly null) for the flow::util::Task_engine used by any coming async I/O calls and inherited by any
   * subsequently generated Peer_socket objects.
   *
   * One, this is used by `this->async_accept()` I/O calls, both
   * synchronously and during the async phases of such calls, whenever placing a user-supplied handler routine
   * onto an `Task_engine`.  Whatever async_task_engine() returns at that time is the `Task_engine` used.
   *
   * Two, when sync_accept() creates Peer_socket, at that moment this is used.  Hence, the initial value
   * returned by Peer_socket::async_task_engine() is thus inherited from
   * the factory Server_socket's async_task_engine() result.
   *
   * ### Thread safety (operating on a given Node) ###
   *
   *   - set_async_task_engine() is the only way to change the returned value, upon construction.
   *   - It is not safe to call set_async_task_engine() at the same time as async_task_engine().
   *   - Define "any async op" as Server_socket::async_accept() and all its variants, including actions it takes
   *     in the background (asynchronously).
   *     - async_task_engine() must return the same value -- and *not* null -- throughout "any async op."
   *   - Define "any async inheriting op" as any (non-blocking) Server_socket::accept() method (which creates a
   *     Peer_socket.
   *     - async_task_engine() must return the same value -- possibly null -- throughout "any async inheriting op."
   *   - Put simply, a null `Task_engine` can be inherited by a generated socket, but a null cannot be used when
   *     performing an async op.  Furthermore, set_async_task_engine() is unsafe through any of those and through
   *     async_task_engine() itself.
   *
   * Informal tip: Typically you'd never call set_async_task_engine(), hence there is no problem.  If you DO need to
   * call it, even then normally it's easy to ensure one does this before any actual async calls are made.
   * Trouble only begins if one calls set_async_task_engine() "in the middle" of operating the socket.
   * There is no way to add outside locking to make that work, either, due to async "tails" of some calls.
   *
   * @return Null or non-null pointer to `Task_engine`.
   */
  util::Task_engine* async_task_engine();

  /**
   * Read-only version of async_task_engine().
   *
   * @return Reference to read-only `*(async_task_engine())`.
   */
  const util::Task_engine& async_task_engine_cref() const;

  /**
   * Overwrites the value to be returned by next async_task_engine().
   *
   * See async_task_engine() doc header before using this.
   *
   * @param target_async_task_engine
   *        See async_task_engine().
   */
  void set_async_task_engine(util::Task_engine* target_async_task_engine);

  /**
   * boost.asio-style asynchronous version that essentially performs
   * net_flow::Server_socket::sync_accept() in the background and invokes the given handler via the saved
   * `Task_engine *(async_task_engine())`, as if by `post(Task_engine&)`.
   *
   * The semantics are identical to the similar `sync_accept()`, except that the operation does not block the
   * calling thread; and the results are delivered to the supplied handler `on_result` instead of to the caller.
   *
   * @tparam Rep
   *         See net_flow::Server_socket::accept().
   * @tparam Period
   *         See net_flow::Server_socket::accept().
   * @tparam Handler
   *         A type such that if `Handler h`, then a function equivalent to `{ h(err_code, sock); }` can
   *         be `post()`ed onto an `Task_engine`, with `const Error_code& err_code` and
   *         `net_flow::asio::Peer_socket sock`.
   * @param on_result
   *        Handler to be executed asynchronously within the saved `Task_engine`.
   *        The error code and socket values passed to it, in that order, are identical
   *        to those out-arg/returned values in net_flow::Server_socket::sync_accept().
   *        Note: Use `bind_executor(S, F)` to bind your handler to the util::Strand `S`.
   * @param reactor_pattern
   *        See net_flow::Server_socket::sync_accept().
   * @param max_wait
   *        See net_flow::Server_socket::sync_accept().
   */
  template<typename Rep, typename Period, typename Handler>
  void async_accept(const boost::chrono::duration<Rep, Period>& max_wait,
                    bool reactor_pattern,
                    const Handler& on_result);

  /**
   * Equivalent to `async_accept(duration::max(), false, on_result)`; i.e., `async_accept()`
   * with no timeout and in normal -- non-reactor-pattern -- handler mode.
   *
   * @tparam Handler
   *         See other async_accept().
   * @param on_result
   *        See other async_accept().
   */
  template<typename Handler>
  void async_accept(const Handler& on_result);

  /**
   * Equivalent to `async_accept(max_wait, false, on_result)`; i.e., `async_accept()`
   * with in normal -- non-reactor-pattern -- handler mode.
   *
   * @tparam Handler
   *         See other async_accept().
   * @param on_result
   *        See other async_accept().
   * @param max_wait
   *        See other async_accept().
   */
  template<typename Rep, typename Period, typename Handler>
  void async_accept(const boost::chrono::duration<Rep, Period>& max_wait,
                    const Handler& on_result);

  /**
   * Equivalent to `async_accept(duration::max(), reactor_pattern, on_result)`; i.e., `async_accept()`
   * with no timeout.
   *
   * @tparam Handler
   *         See other async_accept().
   * @param on_result
   *        See other async_accept().
   * @param reactor_pattern
   *        See other async_accept().
   */
  template<typename Handler>
  void async_accept(bool reactor_pattern, const Handler& on_result);

  /**
   * Convenience method that polymorphically casts from `net_flow::Server_socket::Ptr` to
   * subclass pointer `net_flow::asio::Server_socket::Ptr`.
   * Behavior undefined if `serv` is not actually of the latter type.
   * With this, one needn't do verbose `static_[pointer_]cast<>` or similar conversions.  For example:
   * `auto serv = asio::Server_socket::cast(node.listen(...));` where `node` is a `net_flow::asio::Node&`,
   * and `serv` is auto-typed as `net_flow::asio::Server_socket::Ptr`.
   *
   * @param serv
   *        Handle to a `serv` such that the true underlying concrete type is net_flow::asio::Server_socket.
   * @return See above.
   */
  static Ptr cast(net_flow::Server_socket::Ptr serv);

private:
  // Friends.

  /// Similarly to the equivalent `friend` in net_flow::Peer_socket.
  friend class Node;
  /// As of this writing, it is needed for Node::serv_create_forward_plus_ctor_args().
  friend class net_flow::Node;

  // Types.

  /// Short-hand for the `Task_engine`-compatible accept `Handler` concrete type for class-internal code.
  using Handler_func = Function<void (const Error_code& err_code, Peer_socket::Ptr new_sock)>;

  // Constructors.

  /**
   * Constructs object.
   *
   * @param logger
   *        See superclass.
   * @param child_sock_opts
   *        See superclass.
   */
  explicit Server_socket(log::Logger* logger, const Peer_socket_options* child_sock_opts);

  // Methods.

  /**
   * De-templated implementation of all `async_accept()` methods.
   *
   * @param on_result
   *        `handler_func(on_result)`, where `on_result` is the user's `async_*()` method arg.
   * @param wait_until
   *        See `max_wait` arg on the originating `async_accept()` method.  This is absolute timeout time point
   *        derived from it; zero-valued if no timeout.
   * @param reactor_pattern
   *        See `async_accept()`.
   */
  void async_accept_impl(Handler_func&& on_result, const Fine_time_pt& wait_until, bool reactor_pattern);

  /**
   * Returns a functor that essentially performs `post()` `on_result` onto `*async_task_engine()` in a way suitable
   * for a boost.asio-compatible async-op.
   *
   * ### Rationale ###
   * See asio::Peer_socket::handler_func().
   *
   * @tparam Handler
   *         See async_accept().
   * @param on_result
   *        See async_accept().
   * @return Function to call from any context that will properly `post()` `on_result();` onto `*async_task_engine()`.
   */
  template<typename Handler>
  Handler_func handler_func(Handler&& on_result);

  // Data.

  /// See async_task_engine().
  util::Task_engine* m_target_task_engine;
}; // class asio::Server_socket

// Free functions: in *_fwd.hpp.

// Template implementations.

template<typename Rep, typename Period, typename Handler>
void Server_socket::async_accept(const boost::chrono::duration<Rep, Period>& max_wait,
                                 bool reactor_pattern,
                                 const Handler& on_result)
{
  async_accept_impl(Handler_func(on_result),
                    util::chrono_duration_from_now_to_fine_time_pt(max_wait),
                    reactor_pattern);
}

template<typename Handler>
void Server_socket::async_accept(const Handler& on_result)
{
  async_accept_impl(Handler_func(on_result), Fine_time_pt(), false);
}

template<typename Handler>
void Server_socket::async_accept(bool reactor_pattern, const Handler& on_result)
{
  async_accept_impl(Handler_func(on_result), Fine_time_pt(), reactor_pattern);
}

template<typename Rep, typename Period, typename Handler>
void Server_socket::async_accept(const boost::chrono::duration<Rep, Period>& max_wait,
                                 const Handler& on_result)
{
  async_accept_impl(Handler_func(on_result),
                    util::chrono_duration_from_now_to_fine_time_pt(max_wait),
                    false);
}

template<typename Handler>
Server_socket::Handler_func Server_socket::handler_func(Handler&& on_result)
{
  using boost::asio::post;
  using boost::asio::bind_executor;
  using boost::asio::get_associated_executor;

  /* This mirrors Peer_socket::handler_func() exactly (just different signature on on_result()).  Comments light.
   * @todo Maybe there's a way to generalize this with template param-packs or something. */

  return [this, on_result = std::move(on_result)]
           (const Error_code& err_code, Peer_socket::Ptr new_sock)
           mutable
  {
    const auto executor = get_associated_executor(on_result);
    post(*(async_task_engine()),
         bind_executor(executor,
                       [err_code, new_sock, on_result = std::move(on_result)]
    {
      on_result(err_code, new_sock);
    }));
  };
} // Server_socket::handler_func()

} // namespace flow::net_flow::asio
